package web.search;

import cn.zbmao.fc.FcTool;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.document.*;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 搜索工具类
 * User: Winter Lau
 * Date: 13-1-10
 * Time: 上午11:54
 */
public class SearchHelper {

    private final static Logger log = LoggerFactory.getLogger(SearchHelper.class);
    private final static Analyzer analyzer = new CJKAnalyzer(Version.LUCENE_46);
    private final static BooleanQuery nullQuery = new BooleanQuery();
    private final static Formatter highlighter_formatter = new SimpleHTMLFormatter("<span class=\"highlight\">", "</span>");

    public final static String FN_ID = "___id";
    public final static String FN_CLASSNAME = "___class";

    private final static List<String> nowords = new ArrayList<String>() {{
        try {
            addAll(IOUtils.readLines(SearchHelper.class.getResourceAsStream("/stopword.dic")));
        } catch (IOException e) {
            log.error("Unabled to read stopword file", e);
        }
    }};

    private final static List<String> ReserveKeys = new ArrayList<String>() {{
        try {
            addAll(IOUtils.readLines(SearchHelper.class.getResourceAsStream("/keywords.dic")));
        } catch (IOException e) {
            log.error("Unabled to read keywords file", e);
        }
    }};

    /**
     * 重整搜索关键短语
     *
     * @param key
     * @return
     */
    public static String cleanupKey(String key) {
        if (ReserveKeys.contains(key.trim().toLowerCase()))
            return key;

        StringBuilder sb = new StringBuilder();
        List<String> keys = splitKeywords(key);
        for (String word : keys) {
            if (sb.length() > 0)
                sb.append(' ');
            sb.append(word);
        }

        return sb.toString();
    }

    /**
     * 生成查询条件
     *
     * @param field
     * @param q
     * @param boost
     * @return
     */
    public static Query makeQuery(String field, String q, float boost) {
        if (StringUtils.isBlank(q) || StringUtils.isBlank(field))
            return nullQuery;
        QueryParser parser = new QueryParser(Version.LUCENE_46, field, analyzer);
        parser.setDefaultOperator(QueryParser.AND_OPERATOR);
        try {
            Query querySinger = parser.parse(q);
            querySinger.setBoost(boost);
            //System.out.println(querySinger.toString());
            return querySinger;
        } catch (Exception e) {
            TermQuery queryTerm = new TermQuery(new Term(field, q));
            queryTerm.setBoost(boost);
            //System.out.println(queryTerm.toString());
            return queryTerm;
        }
    }

    /**
     * 生成查询条件
     *
     * @param field
     * @param q
     * @param boost
     * @return
     */
    public static Query phraseQuery(String field, String q, float boost) {
        if (StringUtils.isBlank(q) || StringUtils.isBlank(field))
            return nullQuery;
        PhraseQuery phraseQuery = new PhraseQuery();
        try {
            String [] fields= FcTool.getFc().indexFc(q).split(" ");
            for (String s : fields) {
                phraseQuery.add(new Term(field,s));
            }

            phraseQuery.setSlop(0);
            phraseQuery.setBoost(boost);
            return phraseQuery;
        } catch (Exception e) {
            TermQuery queryTerm = new TermQuery(new Term(field, q));
            queryTerm.setBoost(boost);
            return queryTerm;
        }
    }

    /**
     * 生成查询条件
     *
     * @param fields
     * @param qs
     * @param boost
     * @return
     */
    public static Query phraseQuery(String[] fields, String[] qs,BooleanClause.Occur[] flags, float boost) {


        if (fields==null || qs==null )
            return nullQuery;
        if (fields.length==1){
            return phraseQuery(fields[0], qs[0], boost);
        }

        BooleanQuery booleanQuery = new BooleanQuery();

        for (int i=0 ; i< fields.length ;i++) {
            if (StringUtils.isBlank(fields[i])){
                return nullQuery;
            }
            PhraseQuery phraseQuery = new PhraseQuery();
            try {
                String [] field_s= FcTool.getFc().indexFc(qs[i]).split(" ");
                for (String s : field_s) {
                    phraseQuery.add(new Term(fields[i],s));
                }
                phraseQuery.setSlop(0);
                booleanQuery.add(phraseQuery, flags[i]);
            } catch (Exception e){
                log.error(e.getMessage(),"搜索生成查询条件异常，qs:"+qs.toString()+"\n flags:"+flags.toString());
                return nullQuery;
            }

        }
        booleanQuery.setBoost(boost);
        return booleanQuery;
    }

    /**
     * 生成查询条件
     *
     * @param fields
     * @param qs
     * @param boost
     * @return
     */
    public static Query makeQuery(String[] fields, String[] qs,BooleanClause.Occur[] flags, float boost) {


        if (fields==null || qs==null )
            return nullQuery;
        if (fields.length==1){
            return makeQuery(fields[0],qs[0],boost);
        }

        BooleanQuery booleanQuery = new BooleanQuery();

        for (int i=0 ; i< fields.length ;i++) {
            if (StringUtils.isBlank(fields[i])){
                return nullQuery;
            }
            QueryParser parser = new QueryParser(Version.LUCENE_46,fields[i],new CJKAnalyzer(Version.LUCENE_46));
            parser.setDefaultOperator(QueryParser.AND_OPERATOR);
            Query contentQuery = null;
            try {
                contentQuery = parser.parse(qs[i]);
                booleanQuery.add(contentQuery, flags[i]);
            } catch (Exception e){
                log.error(e.getMessage(),"搜索生成查询条件异常，qs:"+qs.toString()+"\n flags:"+flags.toString());
                return nullQuery;
            }

        }
        booleanQuery.setBoost(boost);
        return booleanQuery;
    }

    public static final int TYPE_TEXT = -1;
    public static final int TYPE_AND = 0;
    public static final int TYPE_OR = 1;
    public static final int TYPE_NO = 1;

    public static Query makeQuery(String[] fields, String[] qs,BooleanClause.Occur[] flags, float boost,int[] types){
        if (fields==null || qs==null )
            return nullQuery;
        if (fields.length==1){
            return makeQuery(fields[0],qs[0],boost);
        }


        BooleanQuery booleanQuery = new BooleanQuery();
        for(int i = 0 ; i < types.length ; i++){
            if (StringUtils.isBlank(fields[i])){
                return nullQuery;
            }
            if (types[i]==TYPE_TEXT){
                QueryParser parser = new QueryParser(Version.LUCENE_46,fields[i],new CJKAnalyzer(Version.LUCENE_46));
                parser.setDefaultOperator(QueryParser.AND_OPERATOR);
                Query contentQuery = null;
                try {
                    contentQuery = parser.parse(qs[i]);
                } catch (ParseException e) {
                    log.error(e.getMessage(), "搜索生成查询条件异常，qs:" + qs.toString() + "\n flags:" + flags.toString());
                    return nullQuery;
                }
                booleanQuery.add(contentQuery,flags[i]);
            }else if (types[i]==TYPE_AND){
                TermQuery termQuery = new TermQuery(new Term(fields[i],qs[i]));
                booleanQuery.add(termQuery,flags[i]);
            }else if(types[i]==TYPE_OR){
                TermQuery termQuery = new TermQuery(new Term(fields[i],qs[i]));
                booleanQuery.add(termQuery,flags[i]);
            }else if(types[i]==TYPE_NO){
                TermQuery termQuery = new TermQuery(new Term(fields[i],qs[i]));
                booleanQuery.add(termQuery, BooleanClause.Occur.MUST_NOT);
            }
        }

        booleanQuery.setBoost(boost);
        return booleanQuery;
    }
    public static Query makeQuery(String[] fields, String[] qs, float boost,int[] types){
        if (fields==null || qs==null )
            return nullQuery;
        if (fields.length==1){
            return makeQuery(fields[0],qs[0],boost);
        }


        BooleanQuery booleanQuery = new BooleanQuery();
        for(int i = 0 ; i < types.length ; i++){
            if (StringUtils.isBlank(fields[i])){
                return nullQuery;
            }
            if (types[i]==TYPE_TEXT){
                QueryParser parser = new QueryParser(Version.LUCENE_46,fields[i],new CJKAnalyzer(Version.LUCENE_46));
                parser.setDefaultOperator(QueryParser.AND_OPERATOR);
                Query contentQuery = null;
                try {
                    contentQuery = parser.parse(qs[i]);
                } catch (ParseException e) {
                    log.error(e.getMessage(),"搜索生成查询条件异常，qs:"+qs.toString()+"\n ");
                    return nullQuery;
                }
                booleanQuery.add(contentQuery, BooleanClause.Occur.MUST);
            }else if (types[i]==TYPE_AND){
                TermQuery termQuery = new TermQuery(new Term(fields[i],qs[i]));
                booleanQuery.add(termQuery, BooleanClause.Occur.MUST);
            }else if(types[i]==TYPE_OR){
                TermQuery termQuery = new TermQuery(new Term(fields[i],qs[i]));
                booleanQuery.add(termQuery, BooleanClause.Occur.SHOULD);
            }else if(types[i]==TYPE_NO){
                TermQuery termQuery = new TermQuery(new Term(fields[i],qs[i]));
                booleanQuery.add(termQuery, BooleanClause.Occur.MUST_NOT);
            }
        }

        booleanQuery.setBoost(boost);
        return booleanQuery;
    }

    /**
     * 生成查询条件
     *
     * @param fields
     * @param qs
     * @param boost
     * @return
     */
    public static Query makeQuery(String[] fields, String[] qs, float boost) {

        if (fields==null || qs==null )
            return nullQuery;
        if (fields.length==1){
            return makeQuery(fields[0],qs[0],boost);
        }

        BooleanQuery booleanQuery = new BooleanQuery();

        for (int i=0 ; i< fields.length ;i++) {
            if (StringUtils.isBlank(fields[i])){
                return nullQuery;
            }
            QueryParser parser = new QueryParser(Version.LUCENE_46,fields[i],new CJKAnalyzer(Version.LUCENE_46));
            parser.setDefaultOperator(QueryParser.AND_OPERATOR);
            Query contentQuery = null;
            try {
                contentQuery = parser.parse(qs[i]);
            } catch (Exception e){
                log.error(e.getMessage(),"搜索生成查询条件异常，qs:"+qs.toString());
                return nullQuery;
            }
            booleanQuery.add(contentQuery, BooleanClause.Occur.MUST);
        }
        booleanQuery.setBoost(boost);
        return booleanQuery;
    }

    /**
     * 关键字切分
     *
     * @param sentence 要分词的句子
     * @return 返回分词结果
     */
    public static List<String> splitKeywords(String sentence) {

        List<String> keys = new ArrayList<String>();

        if (StringUtils.isNotBlank(sentence)) {
            StringReader reader = new StringReader(sentence);

            TokenStream tokenStream = null;
            try {
                tokenStream = analyzer.tokenStream("",reader);
                tokenStream.reset();
                CharTermAttribute offAtt = tokenStream.addAttribute(CharTermAttribute.class);

                while(tokenStream.incrementToken()){
                    String term=offAtt.toString();

                    keys.add(term);
                }
            } catch (IOException e) {
                log.info(e + "");
            }
        }

        return keys;
    }

    /**
     * 对一段文本执行语法高亮处理
     *
     * @param text 要处理高亮的文本
     * @param key  高亮的关键字
     * @return 返回格式化后的HTML文本
     */
    public static String highlight(String text, String key) {
        if (StringUtils.isBlank(key) || StringUtils.isBlank(text))
            return text;
        String result = null;
        try {
            PhraseQuery pquery = new PhraseQuery();
            for (String sk : splitKeywords(key)) {
                pquery.add(new Term("", QueryParser.escape(sk)));
            }
            QueryScorer scorer = new QueryScorer(pquery);
            Highlighter hig = new Highlighter(highlighter_formatter, scorer);
            TokenStream tokens = analyzer.tokenStream(null, new StringReader(text));
            result = hig.getBestFragment(tokens, text);
        } catch (Exception e) {
            log.error("Unabled to hightlight text", e);
        }
        return (result != null) ? result : text;
    }

    /**
     * 返回文档中保存的对象 id
     *
     * @param doc
     * @return
     */
    public static long docid(Document doc) {
        return NumberUtils.toLong(doc.get(FN_ID), 0);
    }

    /**
     * 获取文档对应的对象类
     *
     * @param doc
     * @return
     * @throws ClassNotFoundException
     */
    public static Searchable doc2obj(Document doc) {
        try {
            long id = docid(doc);
            if (id <= 0)
                return null;
            Searchable obj = (Searchable) Class.forName(doc.get(FN_CLASSNAME)).newInstance();
            obj.setId(id);

            /************增加****************/
            //属性设置
            List<String> fields = obj.storeFields();
            for(String field : fields) {
                if (field.equals("id"))continue;
                PropertyUtils.setProperty(obj, field, doc.get(field));
            }

            return obj;
        } catch (Exception e) {
            log.error("Unabled generate object from document#id=" + doc.toString(), e);
            return null;
        }
    }

    /**
     * 将对象转成 Lucene 的文档
     *
     * @param obj Java 对象
     * @return 返回Lucene文档
     */
    public static Document obj2doc(Searchable obj) {
        if (obj == null)
            return null;

        Document doc = new Document();
        doc.add(new LongField(FN_ID, obj.id(), Field.Store.YES));
        doc.add(new StoredField(FN_CLASSNAME, obj.getClass().getName()));

        //存储字段
        List<String> fields = obj.storeFields();
        if (fields != null)
            for (String fn : fields) {
                Object fv = readField(obj, fn);
                if (fv != null)
                    doc.add(obj2field(fn, fv, true));
            }

        //扩展存储字段
        Map<String, String> eDatas = obj.extendStoreDatas();
        if (eDatas != null)
            for (String fn : eDatas.keySet()) {
                if (fields != null && fields.contains(fn))
                    continue;
                String fv = eDatas.get(fn);
                if (fv != null)
                    doc.add(obj2field(fn, fv, true));
            }

        //索引字段
        fields = obj.indexFields();
        if (fields != null)
            for (String fn : fields) {
                String fv = (String) readField(obj, fn);
                if (fv != null) {

                   TextField tf = new TextField(fn, fv, Field.Store.NO);
                   tf.setBoost(obj.boost());
                   doc.add(tf);

                }
            }

        //扩展索引字段
        eDatas = obj.extendIndexDatas();
        if (eDatas != null)
            for (String fn : eDatas.keySet()) {
                if (fields != null && fields.contains(fn))
                    continue;
                String fv = eDatas.get(fn);
                if (fv != null) {
                    TextField tf = new TextField(fn, fv, Field.Store.NO);
                    tf.setBoost(obj.boost());
                    doc.add(tf);
                }
            }

        return doc;
    }

    /**
     * 访问对象某个属性的值
     *
     * @param obj   对象
     * @param field 属性名
     * @return Lucene 文档字段
     */
    private static Object readField(Object obj, String field) {
        try {
            return PropertyUtils.getProperty(obj, field);
        } catch (Exception e) {
            log.error("Unabled to get property '" + field + "' of " + obj.getClass().getName(), e);
            return null;
        }

    }

    private static Field obj2field(String field, Object fieldValue, boolean store) {
        if (fieldValue == null)
            return null;

        if (fieldValue instanceof Date) //日期
            return new LongField(field, ((Date) fieldValue).getTime(), store ? Field.Store.YES : Field.Store.NO);
        if (fieldValue instanceof Number) //其他数值
            return new StringField(field, String.valueOf(((Number) fieldValue).longValue()), store ? Field.Store.YES : Field.Store.NO);
        //其他默认当字符串处理
        return new StringField(field, (String) fieldValue, store ? Field.Store.YES : Field.Store.NO);
    }

}
